/*
 * 版权所有 2009-2022山东新北洋信息技术股份有限公司 保留所有权利。
 */
package online.xuping.thousandsOfWorlds.common.importexcel;

import cn.hutool.core.collection.CollUtil;
import com.alibaba.druid.util.StringUtils;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import online.xuping.thousandsOfWorlds.common.exception.BusinessRuntimeException;
import online.xuping.thousandsOfWorlds.common.importexcel.domain.ImportErrorVO;
import online.xuping.thousandsOfWorlds.common.importexcel.strategy.ParamCheckServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.ReflectionUtils;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * excel导入监听器
 *
 * @author dongyinggang
 * @date 2022/8/18 8:07
 * @copyright 2022 山东新北洋信息技术股份有限公司. All rights reserved
 */
@Slf4j
public class ExcelImportListener<T extends ImportErrorVO> extends AnalysisEventListener<T> {

    /**
     * 泛型的实际类型
     */
    private final Class tClass;

    private final List<T> errorList = new ArrayList<>();

    private final List<T> correctList = new ArrayList<>();

    /**
     * 表头map
     */
    private final Map<Integer, String> globalHeadMap = new HashMap<>();

    private final List<String> headErrorList = new ArrayList<>();


    /**
     * 读取行数
     */
    private int rowCount;

    /**
     * 最大读取行数
     */
    private int maxRowCount;


    /**
     * 有参构造
     * 通过传入的class明确T的类
     *
     * @param tClass :
     * @author dongyinggang
     * @date 2022/9/29 20:25
     **/
    public ExcelImportListener(Class tClass) {
        this.tClass = tClass;
    }

    public List<String> getHeadError() {
        return headErrorList;
    }

    public List getErrorList() {
        return errorList;
    }

    public List getCorrectList() {
        return correctList;
    }

    /**
     * 获取表头map
     *
     * @return 表头map
     * @author dongyinggang
     * @date 2023/6/13 17:40
     **/
    public Map<Integer, String> getGlobalHeadMap() {
        return globalHeadMap;
    }


    /**
     * invokeHeadMap 方法是 表头验证
     *
     * @param headMap 表头的map，其中key为excel列标，从0开始，value为表头值
     * @param context 上下文
     * @author dongyinggang
     * @date 2022/1/18 16:13
     */
    @Override
    public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
        log.info("表头校验开始 ,参数为：{}", headMap);
        //将表头信息存入全局变量
        globalHeadMap.putAll(headMap);
        //判断表头是否为空
        if (CollUtil.isEmpty(headMap)) {
            headErrorList.add("表头为空,请下载模板填写数据");
            return;
        }
        Map<Integer, String> head = new HashMap<>();
        //通过class获取到使用@ExcelProperty注解配置的字段
        try {
            head = getIndexNameMap(tClass);
        } catch (NoSuchFieldException e) {
            log.error("获取表头校验信息失败！", e);
        }
        Set<Integer> keySet = head.keySet();  //解析到的excel表头和实体配置的进行比对
        //判断传入文件表头和VO中定义的列是否长度一致
        if (headMap.size() != keySet.size()) {
            headErrorList.add("表头列数与模板不一致，请下载模板填写数据");
            return;
        }
        //逐个比对表头和VO中定义的列是否一致
        for (Integer key : keySet) {
            //判断VO中定义的列在表头中为空,为空直接提示该列为空，直接进行下一列校验
            if (StringUtils.isEmpty(headMap.get(key))) {
                headErrorList.add("表头第" + key + "列为空");
                continue;
            }
            //不为空的时候去判断是否和VO中定义的列名是否一致
            if (!headMap.get(key).equals(head.get(key))) {
                headErrorList.add("表头第" + key + "列【" + headMap.get(key) + "】与模板的【" + head.get(key) + "】不一致");
            }
        }
    }

    /**
     * 设置最大行数
     *
     * @param maxRowCount 最大行数
     */
    public void setMaxRowCount(int maxRowCount) {
        this.maxRowCount = maxRowCount;
    }

    @Override
    public void invoke(T t, AnalysisContext analysisContext) {
        // 每读取一行数据调用一次
        rowCount++;
        //读取首行数据时打印最大行数
        if (rowCount == 1) {
            log.info("开始解析数据，可读取最大行数: {}", maxRowCount);
        }
        //最大行数为0时不限制，判断行数是否超过限制
        if (maxRowCount != 0 && rowCount > maxRowCount) {
            //超过上线报错
            throw new BusinessRuntimeException("上传上限为: " + maxRowCount);
        }


        try {
            //valid框架校验
            String errorMessage = ParamCheckServiceImpl.validateEntity(t);
            //业务校验
            String customErrorMessage = errorMessage + t.customVerify();
            if (!customErrorMessage.isEmpty()) {
                t.setErrorMessage(customErrorMessage);
                errorList.add(t);
            } else {
                correctList.add(t);
            }
        } catch (NoSuchFieldException e) {
            log.error("设置解析结果失败！错误原因为：", e);
        }
    }


    @Override
    public void doAfterAllAnalysed(AnalysisContext analysisContext) {
        //看情况重写导入完毕之后的操作
    }

    /**
     * 根据传入的对象的信息，获取导入的模板的index和表头名称
     *
     * @param tClass :泛型对应的class
     * @return {@link Map< Integer, String> } 返回index和表头名称的map
     * @author yujian
     * @date 2022/8/23 10:32
     **/
    public Map<Integer, String> getIndexNameMap(Class<T> tClass) throws NoSuchFieldException {
        Map<Integer, String> result = new HashMap<>();
        Field field;
        //获取类中所有的属性
        Field[] fields = tClass.getDeclaredFields();
        for (Field item : fields) {
            field = tClass.getDeclaredField(item.getName());
            ReflectionUtils.makeAccessible(field);
            //获取根据注解的方式获取ExcelProperty修饰的字段
            ExcelProperty excelProperty = field.getAnnotation(ExcelProperty.class);
            if (excelProperty != null) {
                //索引值
                int index = excelProperty.index();
                //字段值
                String[] values = excelProperty.value();
                StringBuilder value = new StringBuilder();
                for (String v : values) {
                    value.append(v);
                }
                result.put(index, value.toString());
            }
        }
        return result;
    }

}
